<?php

	/**
	 * @see Bike_Db_Table_Row
	 */
	require_once 'Bike/Db/Table/Row.php';
	
	/**
	 * @see Bike_Initial
	 */
	require_once 'Bike/Initial.php';
	
	/**
	 * @see Bike_Model_Mapper_Abstract
	 */
	require_once 'Bike/Model/Mapper/Abstract.php';
	
	/**
	 * @see Bike_Db_Table
	 */
	require_once 'Bike/Db/Table.php';
	
	/**
	 * @see Zend_Controller_Front
	 */
	require_once 'Zend/Controller/Front.php';
	
	/**
	 * @see Bike_String
	 */
	require_once 'Bike/String.php';
	
	/**
	 * @see Bike_Model_Mapper_FieldParams
	 */
	require_once 'Bike/Model/Mapper/FieldParams.php';
	
	/**
	 * @see Bike_Db_DataType
	 */
	require_once 'Bike/Db/DataType.php';
	
	
	abstract class Bike_Model_Abstract extends Bike_Db_Table_Row
	{
        const MAPPER_CLASS_NAME_PIECE = Bike_Model_Mapper_Abstract::MAPPER_CLASS_NAME_PIECE;
        const MODEL_CLASS_NAME_PIECE = 'Model_';

        protected $_allData;
        protected $_mapperClass;
        protected $_mapper;
        protected $_reflection;
        protected $_dependentModels = array();

        public function __construct(array $Options = null)
        {
        	if(isset($Options['dirtyData']))
        	{        		$this->setDirtyData($Options['dirtyData']);
        	}
        	if(@$Options['mapperClass'])
        	{        		$this->setMapperClass($Options['mapperClass']);
        	}
        	if(@$Options['data'])
        	{
        		$Options['data'] = $this->_filterData($Options['data']);
        	}
        	parent::__construct($Options);
        }

        public function isEmpty()
        {        	return (sizeof($this->_data) == sizeof($this->_primary) && !sizeof($this->getAllData()) && !sizeof($this->getDependentModels()) && $this->isReadOnly());
        }

        protected function unsetEmptyDependentModels()
        {        	foreach($this->getDependentModels() as $key => $Model)
	    	{
	    		if($Model->isEmpty())
	    		{
	    			$this->unsetDependentModel($key);
	    		}
	    	}
	    	return $this;
        }

        public function init()
        {
        	$this->setDependentModels();
        	if(!$this->isReadOnly())
        	{
	        	if(sizeof($this->_data) != sizeof($this->_getTable()->getColumns()) || sizeof($this->getAllData()))
	        	{	        		$this->setReadOnly(True);
	        	}
	    	}
	    	$this->unsetEmptyDependentModels();
	    	if(!sizeof($this->getAllData()))
	    	{	    		$this->setAllData(Null);
	    	}
        }

        protected function setAllData($AllData)
        {        	$this->_allData = $AllData;
        	return $this;
        }

        protected function getAllData($Name=null)
        {        	if(!is_null($Name))
        	{        		if(is_array($this->_allData) && array_key_exists($Name, $this->_allData))
        		{
        			return $this->_allData[$Name];
        		}
        		require_once 'Bike/Model/Exception.php';
        		throw new Bike_Model_Exception('Undefined key \''.$name.'\' in allData');
        	}
        	return (array)$this->_allData;
        }

        protected function unsetAllDataParam($Name)
        {
        	unset($this->_allData[$Name]);
        	return $this;
        }

        protected function _filterData($Data)
        {        	$this->setAllData($Data);
        	$cols = $this->_getTable()->getColumns();
        	$modelData = array();
        	foreach($this->getAllData() as $name => $value)
        	{        		if(in_array($name, $cols))
        		{        			$this->unsetAllDataParam($name);
        			$modelData[$name] = $value;
        		}
        	}
        	return $modelData;
        }

        protected function setDependentModels()
        {        	foreach($this->getMapperDependences() as $dependence)
        	{
				try
				{
					$this->addDependentModel($dependence->getName(), $dependence);
				}
				catch(Bike_Model_Exception $e)
				{
				}
        	}
        }

        protected function unsetDependentModel($Name)
        {        	unset($this->_dependentModels[$Name]);
        	return $this;
        }

        protected function getDependentModels($Name=Null)
        {
        	if(is_null($Name))
        	{
        		return $this->_dependentModels;
        	}
        	return @$this->_dependentModels[$Name];
        }

        protected function getDependentModel($Name)
        {
        	return $this->getDependentModels($Name);
        }

        protected function addDependentModel($Name, $Dependence)
        {        	$data = array();
        	$allData = $Dependence->getFieldNamesFromAliases(array_keys($this->getAllData()), False);
        	if(sizeof($allData))
        	{
        		$data = array();
        		foreach($allData as $alias => $name)
        		{        			$data[$name] = $this->getAllData($alias);
        			$this->unsetAllDataParam($alias);
        		}
        		$readOnly = (bool) max(array((int) $Dependence->getReadOnly(), (int) $this->isReadOnly()));
        		$Model = $Dependence->getMapper()->_createModelFromCleanData($data, (bool) sizeof($this->_cleanData), $readOnly);
        		$this->_dependentModels[$Name] = $Model;
        	}
        	else
        	{        		require_once 'Bike/Model/Exception.php';
        		throw new Bike_Model_Exception("Can not create dependent model without data");
        	}
        }

        protected function setMapperClass($MapperClass)
        {        	if(is_null($MapperClass))
    		{
    			require_once 'Bike/Model/Exception.php';
    			throw new Bike_Model_Exception("Undefined mapper class that you want to set!");
    		}
        	if(is_null($this->_mapperClass))
    		{
    			$this->_mapperClass = $MapperClass;
    			return $this;
    		}
    		require_once 'Bike/Model/Exception.php';
    		throw new Bike_Model_Exception("Mapper class already defined!");
        }

        public function getMapperClass()
        {        	if(is_null($this->_mapperClass))
        	{        		if(!$this instanceof Bike_Model_Common)
        		{
        			$this->_mapperClass	= str_replace(self::MODEL_CLASS_NAME_PIECE, self::MODEL_CLASS_NAME_PIECE.self::MAPPER_CLASS_NAME_PIECE, get_class($this));
        		}
        	}
        	if(is_null($this->_mapperClass))
        	{        		require_once 'Bike/Model/Exception.php';
        		throw new Bike_Model_Exception("Undefined mapper class!");
        	}
        	return $this->_mapperClass;
        }

		public function getMapper()
		{
			return call_user_func(array($this->getMapperClass(), 'getInstance'));
		}
		
		public function _getTable()
		{			if(is_null($this->_table))
			{				$this->_table = $this->getMapper()->getDbTable();
			}
			return parent::_getTable();
		}

		public function getMapperDependences()
		{			return $this->getMapper()->getDependences();
		}

		public function getMapperInnerDependences()
		{
			return $this->getMapper()->getInnerDependences();
		}

		public function getFrontController()
	    {
	    	return Zend_Controller_Front::getInstance();
	    }

	    protected function getReflection()
        {
        	if(is_null($this->_reflection))
        	{
        		require_once 'Zend/Reflection/Class.php';
        		$this->_reflection = new Zend_Reflection_Class($this);
        	}
        	return $this->_reflection;
        }

        protected function getConstant($Name=Null)
	    {
	    	if(is_null($Name))
	    	{
	    		require_once 'Bike/Model/Exception.php';
	    		throw new Bike_Model_Exception('Invalid constant name provided');
	   		}
	   		return $this->getReflection()->getConstant($Name);
	    }

	    protected function getConst($Name)
	    {
	    	$constName = Bike_String::factory($Name)->toConstFromCamelCase();
	    	return $this->getConstant($constName);
	    }

        protected function getConstants($Prefix=Null)
	    {
	    	$constants = $this->getReflection()->getConstants();
	    	if(!is_null($Prefix))
	    	{
	    		$prefixLen = strlen($Prefix);
	    		foreach($constants as $name => $value)
	    		{
	    			if(substr($name, 0, $prefixLen) != $Prefix)
	    			{
	    				unset($constants[$name]);
	    			}
	    		}
	    	}
	    	return $constants;
	    }

        public function __call($MethodName, $Args)
		{
			if(preg_match("/^getConst([\w\d]+)$/s", $MethodName, $matches))
			{
				return $this->getConst($matches[1], $Args);
			}
			if(preg_match("/^getClean([\w\d]+)$/s", $MethodName, $matches))
			{
				$filter = new Zend_Filter();
				$filter->addFilter(new Zend_Filter_Word_CamelCaseToUnderscore())
					   ->addFilter(new Zend_Filter_StringToLower());
				return $this->_cleanData[$filter->filter($matches[1])];
			}
			return parent::__call($MethodName, $Args);
		}

		public function __get($ColumnName)
	    {	    	
	    	try
	    	{	    		return parent::__get($ColumnName);
	    	}
	    	catch(Zend_Db_Table_Row_Exception $e)
	    	{
	    		$filter = new Zend_Filter();
	    		$filter->addFilter(new Zend_Filter_Word_CamelCaseToUnderscore())
	    			   ->addFilter(new Zend_Filter_StringToLower());
	    		
	    		$ColumnName = $filter->filter($ColumnName);
	    		if(array_key_exists($ColumnName, $this->getAllData()))
	    		{	    			return $this->getAllData($ColumnName);
	    		}
	    		$filterToCamelCase = new Zend_Filter_Word_UnderscoreToCamelCase();
	    		foreach($this->getMapperDependences() as $dependence)
	        	{
					try
					{
						$columnName = $dependence->getFieldNameFromAlias($ColumnName, False);
						$methodName = 'get'.$filterToCamelCase->filter($columnName);
						return $this->_dependentModels[$dependence->getName()]->$methodName();
					}
					catch(Bike_Model_Exception $e)
					{
					}
	        	}
	        	require_once "Zend/Db/Table/Row/Exception.php";
	        	throw new Zend_Db_Table_Row_Exception(sprintf('Specified column "%s" is not in the row', $ColumnName));
	    	}
	    }

	    public function __set($ColumnName, $Value)
		{
			try
	    	{
	    		return parent::__set($ColumnName, $Value);
	    	}
	    	catch(Zend_Db_Table_Row_Exception $e)
	    	{
			    $filter = new Zend_Filter();
	    		$filter->addFilter(new Zend_Filter_Word_CamelCaseToUnderscore())
	    			   ->addFilter(new Zend_Filter_StringToLower());
	    		$ColumnName = $filter->filter($ColumnName);
	    		
	    		$filterToCamelCase = new Zend_Filter_Word_UnderscoreToCamelCase();
	    		foreach($this->getMapperDependences() as $dependence)
	        	{
					try
					{
						$columnName = $dependence->getFieldNameFromAlias($ColumnName, False);
						$methodName = 'set'.$filterToCamelCase->filter($columnName);
						return $this->_dependentModels[$dependence->getName()]->$methodName($Value);
					}
					catch(Bike_Model_Exception $e)
					{					}
	        	}
	        	require_once "Zend/Db/Table/Row/Exception.php";
	        	throw new Zend_Db_Table_Row_Exception(sprintf('Specified column "%s" is not in the row', $ColumnName));
			}
		}

		public function __unset($ColumnName)
		{
			try
	    	{
	    		return parent::__unset($ColumnName);
	    	}
	    	catch(Zend_Db_Table_Row_Exception $e)
	    	{
			    $filter = new Zend_Filter();
	    		$filter->addFilter(new Zend_Filter_Word_CamelCaseToUnderscore())
	    			   ->addFilter(new Zend_Filter_StringToLower());
	    		$ColumnName = $filter->filter($ColumnName);
	    		
	    		if(array_key_exists($ColumnName, $this->getAllData()))
	    		{
	    			return $this->unsetAllDataParam($ColumnName);
	    		}
	    		$filterToCamelCase = new Zend_Filter_Word_UnderscoreToCamelCase();
			    foreach($this->getMapperDependences() as $dependence)
	        	{
					try
					{
						$columnName = $dependence->getFieldNameFromAlias($ColumnName, False);
						$methodName = 'unset'.$filterToCamelCase->filter($columnName);
						return $this->_dependentModels[$dependence->getName()]->$methodName();
					}
					catch(Bike_Model_Exception $e)
					{
					}
	        	}
	        	require_once "Zend/Db/Table/Row/Exception.php";
	        	throw new Zend_Db_Table_Row_Exception(sprintf('Specified column "%s" is not in the row', $ColumnName));
			}
		}

		public function __isset($ColumnName)
	    {
	        $result = parent::__isset($ColumnName);
	        if(!$result)
	        {
		        $filter = new Zend_Filter();
	    		$filter->addFilter(new Zend_Filter_Word_CamelCaseToUnderscore())
	    			   ->addFilter(new Zend_Filter_StringToLower());
	    		$ColumnName = $filter->filter($ColumnName);
	    		if(array_key_exists($ColumnName, $this->getAllData()))
	    		{
	    			return True;
	    		}
	    		$filterToCamelCase = new Zend_Filter_Word_UnderscoreToCamelCase();
			    foreach($this->getMapperDependences() as $dependence)
	        	{
					try
					{
						$columnName = $dependence->getFieldNameFromAlias($ColumnName, False);
						$methodName = 'isset'.$filterToCamelCase->filter($columnName);
						return $this->_dependentModels[$dependence->getName()]->$methodName();
					}
					catch(Bike_Model_Exception $e)
					{
					}
	        	}
	      	}
	        return $result;
	    }

		public function __sleep()
	    {
	        return array_merge(array('_mapperClass', '_dependentModels', '_allData'), parent::__sleep());
	    }

	    public function __wakeup()
		{
		    parent::__wakeup();
		    foreach($this->getDependentModels() as $name => $Model)
    		{
                $Model->__wakeup();
    		}
		}
		
		protected function _insert()
		{
			$this->getMapper()->getBeforeInsertHandler($this);
		}
		
		protected function _postInsert()
		{
			$this->getMapper()->getAfterInsertHandler($this);
		}
		
		protected function _update()
		{
			$this->getMapper()->getBeforeUpdateHandler($this);
		}
		
		protected function _postUpdate()
		{
			$this->getMapper()->getAfterUpdateHandler($this);
		}
		
		protected function _delete()
		{
			$this->getMapper()->getBeforeDeleteHandler($this);
		}
		
		protected function _postDelete()
		{
			$this->getMapper()->getAfterDeleteHandler($this);
		}
		
		protected function cascadeAction($Action, $WithTransaction=False)
		{
			foreach($this->getMapperInnerDependences() as $dependence)
			{
				$Model = $this->getDependentModel($dependence->getName());
				$joinPrimary = (array_values($dependence->getParentJoinFields()) === array_values($this->getTable()->getPrimary()));
				if($Action == 'save' && $joinPrimary)
				{
					$primaryKey = $this->getPrimaryKey();
					$joinFields = array_values($dependence->getJoinFields());
					$filter = new Zend_Filter_Word_UnderscoreToCamelCase();
					foreach(array_values($dependence->getParentJoinFields()) as $n => $name)
					{
						$methodName = 'set'.$filter->filter($joinFields[$n]);
						$Model->$methodName($primaryKey[$name]);
					}
				}				
				
				$primaryKey = $Model->$Action($WithTransaction);
				if($Action == 'save' && !$joinPrimary)
				{
					$filter = new Zend_Filter_Word_UnderscoreToCamelCase();
					if(is_array($primaryKey))
					{
						foreach($dependence->getParentJoinFields() as $fieldName)
						{
							$methodName = 'set'.$filter->filter($fieldName);
							$this->$methodName($primaryKey[$fieldName]);
						}
					}
					else
					{
						$fieldName = reset($dependence->getParentJoinFields());
						$methodName = 'set'.$filter->filter($fieldName);
						$this->$methodName($primaryKey);
					}
				}
			}
		}
		
		public function save($WithTransaction=True)
    	{
    		if($WithTransaction)
    		{    			$this->getTable()->getAdapter()->beginTransaction();
    			try
	    		{
	    			if($this->isNew())
	    			{
	    				$result = parent::save();
	    				
	    			}
	    			$this->cascadeAction('save');
	    			$result = parent::save();
		    		$this->getTable()->getAdapter()->commit();
		    		return $result;
	    		}
	    		catch(Exception $e)
				{
					$this->getTable()->getAdapter()->rollBack();
					require_once "Zend/Db/Table/Row/Exception.php";
					throw new Zend_Db_Table_Row_Exception('Can not save the row: '.$e->getMessage(), 0, $e);
		    	}
    		}
    		else
    		{    			$this->cascadeAction('save');
    			return parent::save();
    		}
    	}

    	public function delete($WithTransaction=True)
    	{
    		if($WithTransaction)
    		{
    			$this->getTable()->getAdapter()->beginTransaction();
    			try
	    		{
	    			$this->cascadeAction('delete');
	    			$result = parent::delete();
		    		$this->getTable()->getAdapter()->commit();
		    		return $result;
	    		}
	    		catch(Exception $e)
				{
					$this->getTable()->getAdapter()->rollBack();
					require_once "Zend/Db/Table/Row/Exception.php";
					throw new Zend_Db_Table_Row_Exception('Can not delete the row: '.$e->getMessage(), 0, $e);
		    	}
    		}
    		else
    		{
    			$this->cascadeAction('delete');
    			return parent::delete();
    		}
    	}

    	public function getIterator()
		{
		    return new ArrayIterator($this->toArray());
		}
		
		public function isNew()
		{
			return empty($this->_cleanData);
		}

    	public function toArray($AllData=True)
	    {
	        $dataArray = parent::toArray();
	        foreach($this->getMapperDependences() as $dependence)
        	{
				$Model = $this->getDependentModel($dependence->getName());
				if($Model instanceof Bike_Model_Abstract)
				{					$array = $Model->toArray($AllData);
					$_dataArr = array();
					foreach($array as $fieldName => $value)
					{						$_dataArr[$dependence->getReturnFieldAlias($fieldName)] = $value;
					}
					$dataArray = array_merge($dataArray, $_dataArr);
				}
        	}
	        return array_merge((array) $dataArray, (array) (($AllData) ? $this->getAllData() : array()));
	    }

	    public function setDirtyData($DirtyData)
		{
			$filter = new Zend_Filter_Word_UnderscoreToCamelCase();
			foreach($DirtyData as $columnName => $value)
			{
				$methodName = 'set'.$filter->filter($columnName);
				$this->$methodName($value);
			}
			return $this;
		}

	    public function refresh()
		{
		    $Select = $this->getMapper()->select(array_keys($this->toArray(False)));
		    foreach($this->_primary as $name)
            {            	$Select->where($name.' = ?', $this->$name);
            }
            $Row = $this->getMapper()->fetchRow($Select);
		    $filter = new Zend_Filter_Word_UnderscoreToCamelCase();
		    foreach($Row->toArray(False) as $name => $value)
		    {		    	$methodName = 'set'.$filter->filter($name);
		    	$this->$methodName($value);
		    }
		}

		protected function getDateObject($Date)
		{			require_once 'Bike/Date.php';
			if(!$Date instanceof Bike_Date)
			{
				if($Date instanceof Zend_Date)
				{					$Date = $Date->getTimestamp();
				}
				$Date = new Bike_Date($Date);
			}
			return $Date;
		}

		public function isActive()
		{			try
			{
				return (bool) ($this->getStatusId() == 1);
			}
			catch(Exception $e)
			{				return True;
			}
		}

		public function isInactive()
		{
			try
			{
				return (bool) ($this->getStatusId() == 2);
			}
			catch(Exception $e)
			{
				return False;
			}
		}
		
		public function getUrlParamName()
		{
			return $this->getMapper()->getUrlParamName();
		}
		
		public function getUrlIdArr()
		{
			return array($this->getUrlParamName() => join(';', $this->getPrimaryKey()));
		}
		
		public function getForm()
		{
			$form = $this->getMapper()->getForm();						
			$this->addElementsToForm($form);			
			$form->fixOrderDuplicates();			
			return $form;
		}
		
		public function addElementsToForm(Bike_Form $form, Bike_Model_Mapper_Dependence $dependence = null, array $params = null, $fieldNamePrefix = '')
		{
			$mapper = $this->getMapper();
			$metaData = $mapper->getTableMetaData();
			if(is_null($params))
			{
				$params = $mapper->getFormFieldsParams($this->isNew());
			}

			$mapper->getBeforeAddFormElementsHandler($form, $params, $fieldNamePrefix);
			
			foreach($mapper->getTableColumns() as $fieldName)
			{
				$formElementName = $fieldNamePrefix.$fieldName;
				$fieldParams = (isset($params[$formElementName]) ? $params[$formElementName] : array());
				if(!$fieldParams instanceof Bike_Model_Mapper_FieldParams)
				{
					$fieldParams = Bike_Model_Mapper_FieldParams::factory((array) $fieldParams);
				}
				$fieldParams->setMetaData($metaData[$fieldName])->setFormElementName($formElementName);
				$fieldHandler = Bike_Db_DataType::getHandler($fieldParams);
				$fieldHandler->addElementToForm($form, $this);
			}
		
			foreach($this->getMapperInnerDependences() as $mapperDependence)
			{
				$dependentModel = $this->getDependentModel($mapperDependence->getName());
				if($dependentModel instanceof self)
				{
					$dependentModel->addElementsToForm($form, $mapperDependence, $params, $fieldNamePrefix.$mapperDependence->getAliasPrefix());
				}
				
				foreach($mapperDependence->getJoinFields() as $fieldName)
				{
					$form->removeElement($mapperDependence->getAliasPrefix().$fieldName);
				}
			}
			$mapper->getAfterAddFormElementsHandler($form, $params, $fieldNamePrefix);
			return $form;
		}
	}